package org.apache.ocean.solr.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;

import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.KeepOnlyLastCommitDeletionPolicy;
import org.apache.lucene.index.SerialMergeScheduler;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.NativeFSLockFactory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.ocean.main.Index.IndexSnapshot;
import org.apache.ocean.main.Index.MergedDocMap;
import org.apache.ocean.main.TransactionSystem.Category;
import org.apache.ocean.main.WriteableMemoryIndex.MemoryIndexSnapshot;
import org.apache.ocean.main.fields.SchemaField;
import org.apache.ocean.main.search.cache.Cache;
import org.apache.ocean.main.search.cache.CacheKey;
import org.apache.ocean.main.search.cache.FieldCacheKey;
import org.apache.ocean.main.search.cache.FieldCacheKey.FieldIndex;
import org.apache.ocean.main.util.Constants;

public class RamIndex extends DirectoryIndex {
	private RAMDirectory ramDirectory;
	private Long highestSnapshotId;
	private Long highestDocumentId;
  
	public RamIndex(Long id, List<? extends IndexSnapshot> indexSnapshots, TransactionSystem.Category category, IndexSchema.Category schemaCategory) throws Exception {
		super(id, schemaCategory.getAnalyzer(), category);
		ramDirectory = new RAMDirectory();
		IndexReader[] indexReaders = getIndexReaders(indexSnapshots);
		RAMDirectory ramDirectory = new RAMDirectory();
		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, defaultAnalyzer, true);
		indexWriter.setMergeScheduler(new SerialMergeScheduler());
		indexWriter.setUseCompoundFile(true);
		indexWriter.addIndexes(indexReaders);
		indexWriter.close();
		highestSnapshotId = getHighestSnapshotId(indexSnapshots);
		highestDocumentId = getHighestDocumentId(indexSnapshots);
		cache = initCache();
		Set<CacheKey> cacheKeys = getCacheKeys(indexSnapshots);
		MergedDocMap mergedDocMap = new MergedDocMap(indexSnapshots);
		cache.merge(cacheKeys, mergedDocMap);
		for (FieldIndex fieldIndex : cache.getFieldIndices()) {
			writeFieldIndex(fieldIndex);
		}
	}
	
	// TODO: add timestamp so ramindex can be removed from indices
	public RamIndex(Long id, Long snapshotId, List<Deletes> deletesList, RAMDirectory ramDirectory, TransactionSystem.Category category, IndexSchema.Category schemaCategory) throws Exception {
		super(id, schemaCategory.getAnalyzer(), category);
		this.ramDirectory = ramDirectory;
		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
	  cache = initCache();
	  FieldIndex idFieldIndex = null;
	  for (String fileName : ramDirectory.list()) {
	  	if (fileName.endsWith(".fieldindex")) {
	      FieldIndex fieldIndex = (FieldIndex)DirectoryIndex.readObject(fileName, ramDirectory);
	      cache.replaceFieldIndex(fieldIndex);
	      if (fieldIndex.field.equals(Constants.ID)) {
	      	idFieldIndex = fieldIndex;
	      }
	  	}
	  }
	  assert idFieldIndex != null;
		createNewSnapshot(snapshotId, initialIndexReader, schemaCategory);
		if (deletesList != null) {
			for (Deletes deletes : deletesList) {
				applyDeletes(true, deletes, null, initialIndexReader, idFieldIndex);
			}
		}
	}
  
	// converts memoryIndexSnapshot into ramindexsnapshot
	public RamIndex(Long id, MemoryIndexSnapshot memoryIndexSnapshot) throws Exception, IOException {
		super(id, memoryIndexSnapshot.getIndexSchemaCategory().getAnalyzer(), memoryIndexSnapshot.getIndex().getCategory());
		this.highestSnapshotId = memoryIndexSnapshot.getHighestSnapshotId();
		this.highestDocumentId = memoryIndexSnapshot.getHighestDocumentId();
		ramDirectory = new RAMDirectory();
		IndexWriter indexWriter = new IndexWriter(ramDirectory, false, defaultAnalyzer, true, new KeepOnlyLastCommitDeletionPolicy());
		indexWriter.addIndexes(new IndexReader[] {memoryIndexSnapshot.getIndexReader()});
		indexWriter.close();
		initialIndexReader = IndexReader.open(ramDirectory, indexDeletionPolicy);
		Category.Config config = memoryIndexSnapshot.getIndex().getCategory().getConfig();
		cache = initCache();
		List<IndexSnapshot> indexSnapshots = new ArrayList<IndexSnapshot>(1);
		indexSnapshots.add(memoryIndexSnapshot);
		MergedDocMap mergedDocMap = new MergedDocMap(indexSnapshots);
		cache.merge(memoryIndexSnapshot.getCacheKeys(), mergedDocMap);
		// TODO: create field indices
		IndexSchema.Category schemaCategory = memoryIndexSnapshot.getIndexSchemaCategory();
		assert hasAllFieldIndices(schemaCategory, cache);
		createNewSnapshot(memoryIndexSnapshot.getSnapshotId(), initialIndexReader, schemaCategory);
	}
  
	public RamIndexSnapshot commitIndex(Transaction transaction) throws IndexException, InterruptedException, IOException {
		try {
			transaction.ready(this);
			if (transaction.go()) {
				Long snapshotID = transaction.getId();
				IndexSchema.Category schemaCategory = transaction.getIndexSchemaCategory();
				RamIndexSnapshot indexSnapshot = createNewSnapshot(snapshotID, initialIndexReader, schemaCategory);
				return indexSnapshot;
			} else {
				// if commit fails this snapshot and ramindex won't make it
				return null;
			}
		} catch (Throwable throwable) {
			log.log(Level.SEVERE, "", throwable);
			transaction.failed(this, throwable);
			return null;
		}
	}
	
	protected RamIndexSnapshot createNewSnapshot(Long snapshotId, IndexReader newIndexReader, IndexSchema.Category schemaCategory) throws IOException {
		RamIndexSnapshot ramIndexSnapshot = new RamIndexSnapshot(snapshotId, newIndexReader, schemaCategory);
		registerSnapshot(ramIndexSnapshot);
		return ramIndexSnapshot;
	}

	public class RamIndexSnapshot extends DirectoryIndexSnapshot {
		public RamIndexSnapshot(Long snapshotID, IndexReader indexReader, IndexSchema.Category schemaCategory) {
			super(snapshotID, indexReader, schemaCategory);
		}

		public Long getHighestSnapshotId() throws IOException {
			return highestSnapshotId;
		}

		public Long getHighestDocumentId() throws IOException {
			return highestDocumentId;
		}
	}

	public Directory getDirectory() {
		return ramDirectory;
	}
}
